#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <assert.h>

#include "cache.h"

static struct cache_index *g_pcache_index = NULL;


int cache_index_init(int max_index)
{
    if(max_index < 0)
    {
	fprintf(stderr,"max_index[%d] invalid!\n",max_index);
	exit(EXIT_FAILURE);
    }
    int cache_index_size = sizeof(struct cache_index)+max_index*sizeof(st_cache_index_item);
    g_pcache_index = malloc(cache_index_size);
    if(NULL==g_pcache_index)
    {
	fprintf(stderr,"malloc cache index size[%d] fail:%s!\n",cache_index_size,strerror(errno));
	exit(EXIT_FAILURE);
    }
    memset(g_pcache_index,0,cache_index_size);
    pthread_mutex_init(&g_pcache_index->lock,NULL); 
    g_pcache_index->size = max_index;
    g_pcache_index->num = 0;
    return 0;
}

st_cache_attr *cache_index_find(const char *devid)
{
    assert(g_pcache_index);
    assert(devid);

    st_cache_attr *pattr = NULL;
    pthread_mutex_lock(&g_pcache_index->lock);
    int i = 0;
    for(i=0;i<g_pcache_index->size;i++)
    {
	if(strcmp(devid,g_pcache_index->index[i].devid)==0)
	{
	    pattr = g_pcache_index->index[i].pcache;
	    break;
	}
    }
    pthread_mutex_unlock(&g_pcache_index->lock);
    return pattr;
}

static int cache_attr_init(st_cache_attr *pcache,int num, unsigned int data_cache_size,unsigned int frame_unit_size)
{
    assert(num<=CACHE_TYPE_MAX);

    int k = 0;
    unsigned int cache_size[CACHE_TYPE_MAX]={data_cache_size,data_cache_size/2,data_cache_size/4};
    st_cache_attr *pattr = NULL;
    for(k=0;k<num;k++)
    {
	pattr = pcache+k;
	pthread_rwlock_init(&pattr->rwlock,NULL);

	pattr->data_cache_size = cache_size[k];
	pattr->frame_unit_size = frame_unit_size;

	pattr->data_cache_start = (unsigned char *)malloc(data_cache_size);
	if(NULL==pattr->data_cache_start)
	{
	    fprintf(stderr,"malloc data cache size[%d]B fail!\n",data_cache_size);
	    return -1;
	}
	pattr->data_cache_end = pattr->data_cache_start + data_cache_size;
	memset(pattr->data_cache_start,0,data_cache_size);

	pattr->frame_unit_array = (st_frame_unit *)malloc(sizeof(st_frame_unit)*frame_unit_size); 
	if(NULL == pattr->frame_unit_array)
	{
	    fprintf(stderr,"malloc frame unit size[%d] fail!\n",frame_unit_size);
	    free(pattr->data_cache_start);
	    return -1;
	}
	memset(pattr->frame_unit_array,0,sizeof(st_frame_unit)*frame_unit_size);

	pattr->reader_cnt = -1;
	pattr->write_funit_pos = 0;
	pattr->last_iframe_pos = 0;
	pattr->frame_unit_array[0].paddr = pattr->data_cache_start;

	int i = 0;
	for(i=0;i<MAX_READER_CNT;i++)
	    pattr->reader_funit_pos[i] = -1;
    }

    return 0;
}

extern int cache_add_reader2(const char *devid,int cache_flag);
int cache_index_add2(const char *devid,unsigned int data_cache_size,unsigned int frame_unit_size)
{
    assert(g_pcache_index);
    assert(devid);

    st_cache_attr *pattr = cache_index_find(devid);
    if(pattr)
    {
	fprintf(stderr,"device[%s] cache exists!\n",devid);
	return -1;
    }
    int ret = 0;
    pthread_mutex_lock(&g_pcache_index->lock);
    if(g_pcache_index->num < g_pcache_index->size)
    {
	int i = 0;
	for(i=0;i<g_pcache_index->size;i++)
	{
	    if(g_pcache_index->index[i].devid[0]==0)
	    {
		g_pcache_index->index[i].pcache = calloc(CACHE_TYPE_MAX,sizeof(st_cache_attr));
		pattr = g_pcache_index->index[i].pcache;
		if(NULL==pattr)
		{
		    fprintf(stderr,"malloc cache attr fail!\n");
		    ret = -1;
		}
		else if(cache_attr_init(pattr,CACHE_TYPE_MAX,data_cache_size,frame_unit_size))
		{
		    fprintf(stderr,"cache_attr_init fail!\n");
		    free(pattr);
		    ret = -1;
		}
		else
		{
		    strcpy(g_pcache_index->index[i].devid,devid);
		    g_pcache_index->num++;
		    ret = 0;
		}
		break;
	    }
	}
    }
    else
    {
	fprintf(stderr,"index is full!!\n");
	ret = -1;
    }
    pthread_mutex_unlock(&g_pcache_index->lock);
    if(ret==0)
    {// add the only one device reader
	cache_add_reader2(devid,CACHE_FLAG_TODEVD);
    }
    return ret;
}

int cache_index_add(const char *devid)
{
    return cache_index_add2(devid,5*1024*1024,300);
}

static int cache_attr_exit(st_cache_attr *pattr)
{
    assert(pattr);

    pthread_rwlock_wrlock(&pattr->rwlock);
    if(pattr->data_cache_start)
    {
	free(pattr->data_cache_start);
	pattr->data_cache_start = NULL;
    }
    if(pattr->frame_unit_array)
    {
	free(pattr->frame_unit_array);
	pattr->frame_unit_array = NULL;
    }
    pthread_rwlock_unlock(&pattr->rwlock);
    pthread_rwlock_destroy(&pattr->rwlock);
    memset(pattr,0,sizeof(*pattr));
    return 0;
}

int cache_index_del(const char *devid)
{
    assert(g_pcache_index);
    assert(devid);

    //cache_del_reader2(devid,CACHE_FLAG_TODEVD,0);
    st_cache_attr *pattr = NULL;
    pthread_mutex_lock(&g_pcache_index->lock);
    int i = 0;
    for(i=0;i<g_pcache_index->size;i++)
    {
	if(strcmp(devid,g_pcache_index->index[i].devid)==0)
	{
	    pattr = g_pcache_index->index[i].pcache;
	    g_pcache_index->index[i].devid[0] = 0;
	    g_pcache_index->num--;
	    cache_attr_exit(pattr);
	    free(pattr);
	    pattr = NULL;
	    break;
	}
    }
    pthread_mutex_unlock(&g_pcache_index->lock);
    return 0;
}

int cache_index_exit(void)
{
    assert(g_pcache_index);

    int i = 0;
    pthread_mutex_lock(&g_pcache_index->lock);
    for(i=0;i<g_pcache_index->size;i++)
    {
	if(g_pcache_index->index[i].devid[0]!=0)
	{
	    cache_index_del(g_pcache_index->index[i].devid);
	}
    }
    pthread_mutex_unlock(&g_pcache_index->lock);

    pthread_mutex_destroy(&g_pcache_index->lock);
    free(g_pcache_index);
    g_pcache_index = NULL;
    return 0;
}

static st_cache_attr *cache_get_addr(st_cache_attr *pattr, int flag)
{
    assert(pattr);

    switch(flag&0xffff0000)
    {
	case CACHE_FLAG_STREAM:
	    return pattr+0;
	case CACHE_FLAG_TODEVD:
	    return pattr+1;
	case CACHE_FLAG_TOAPPD:
	    return pattr+2;
	default:
	    fprintf(stderr,"flag [%#x] need CACHE_FLAG_XXXX!\n",flag);
	    return NULL;
    }
    return NULL;
}

int cache_write_frame(const char *devid,int rid,unsigned char *buf,int len,int flag)
{
    assert(g_pcache_index);
    assert(devid);
    assert(buf);

    st_cache_attr *pattr = cache_index_find(devid);
    if(NULL == pattr)
    {
	fprintf(stderr,"device [%s] not exist!\n",devid);
	return -1;
    }
    if(len <= 0)
    {
	fprintf(stderr,"len [%d] invalid!\n",len);
	return -1;
    }

    pattr = cache_get_addr(pattr,flag);
    if(NULL==pattr)
	return -1;

    int ret = 0;
    pthread_rwlock_wrlock(&pattr->rwlock);
    if(pattr->reader_cnt==0)
    {
	ret = CACHE_CLEAR_FLAG;
    }
    else
    {
	st_frame_unit *punit = pattr->frame_unit_array + pattr->write_funit_pos;
	if(CACHE_FLAG_STREAM&flag)
	{
	    punit->reader_id = -1;
	    if(FRAME_FLAG_IUNIT&flag)
	    {
		pattr->frame_unit_array[pattr->last_iframe_pos].next_i_pos = pattr->write_funit_pos;
		pattr->last_iframe_pos = pattr->write_funit_pos;
	    }
	}
	else if(CACHE_FLAG_TOAPPD&flag || CACHE_FLAG_TODEVD&flag)
	{
	    punit->reader_id = rid;
	    pattr->frame_unit_array[pattr->last_iframe_pos].next_i_pos = pattr->write_funit_pos;
	    pattr->last_iframe_pos = pattr->write_funit_pos;
	}
	punit->flag = flag&0xff;
	punit->next_i_pos = -1;
	punit->len = len;
	if(punit->paddr + len > pattr->data_cache_end) 
	    punit->paddr = pattr->data_cache_start;/* For easy,we giveup unenough little idle space in data-cache-end */
	memcpy(punit->paddr,buf,len);

	// update write_funit_pos and it's write-data-cache address
	pattr->write_funit_pos = (pattr->write_funit_pos+1)%pattr->frame_unit_size;
	pattr->frame_unit_array[pattr->write_funit_pos].paddr = punit->paddr + len;
    }
    pthread_rwlock_unlock(&pattr->rwlock);
    return ret;
}


int cache_read_frame(const char *devid,int rdid,unsigned char *buf,int size,int *flag)
{
    assert(g_pcache_index);
    assert(devid);
    assert(buf);

    st_cache_attr *pattr = cache_index_find(devid);
    if(NULL == pattr)
    {
	fprintf(stderr,"device [%s] not exist!\n",devid);
	return -1;
    }
    pattr = cache_get_addr(pattr,*flag);
    if(NULL==pattr)
    {
	fprintf(stderr,"cache_get_addr get flag[%#x] fail!\n",*flag);
	return -1;
    }

    int ret = 0;
    int rid = rdid;
    if(CACHE_FLAG_STREAM&(*flag))
	rid = rdid&0xff;
    else if(CACHE_FLAG_TOAPPD&(*flag))
	rid = (rdid>>8)&0xff;

    pthread_rwlock_rdlock(&pattr->rwlock);
    if(pattr->reader_funit_pos[rid]<0)
    {
	fprintf(stderr,"reader ID [%d] funit pos uninitalized!\n",rid);
	ret = -1;
    }
    else if(pattr->reader_funit_pos[rid] == pattr->write_funit_pos)
    {
	fprintf(stderr,"cache is empty,wait frame..!\n");
	ret = 0;
    }
    else 
    {
	if(CACHE_FLAG_STREAM&(*flag))
	{
	    int iframe_gap = 0; 
	    if(pattr->write_funit_pos < pattr->reader_funit_pos[rid])
	    {
		iframe_gap = ((pattr->write_funit_pos+pattr->frame_unit_size) - pattr->reader_funit_pos[rid]);
	    }
	    else
	    {
		iframe_gap = (pattr->write_funit_pos - pattr->reader_funit_pos[rid]);
	    }
	    if(iframe_gap >= 4*MAX_FRAME_RATE)
	    {// catch up writer speed
		pattr->reader_funit_pos[rid] = pattr->last_iframe_pos;
	    }
	}

	while(pattr->reader_funit_pos[rid] != pattr->write_funit_pos)
	{ /* read from cache */
	    st_frame_unit *punit = pattr->frame_unit_array + pattr->reader_funit_pos[rid];
	    if(size < punit->len)
	    {
		fprintf(stderr,"reader buf size[%d] < frame size[%d]!\n",size,punit->len);
		ret = -1;
		break;
	    }
	    else if((punit->reader_id==rdid || punit->reader_id<0) && (*flag)&punit->flag)
	    {
		*flag = punit->flag;
		memcpy(buf,punit->paddr,punit->len);
		pattr->reader_funit_pos[rid] = (pattr->reader_funit_pos[rid]+1)%pattr->frame_unit_size;
		ret = punit->len;
		break;
	    }
	    else
	    {
		pattr->reader_funit_pos[rid] = (pattr->reader_funit_pos[rid]+1)%pattr->frame_unit_size;
		ret = 0;
	    }
	}
    }
    pthread_rwlock_unlock(&pattr->rwlock);
    return ret;
}

int cache_add_reader2(const char *devid,int cache_flag)
{
    assert(g_pcache_index);
    assert(devid);

    st_cache_attr *pattr = cache_index_find(devid);
    if(NULL == pattr)
    {
	fprintf(stderr,"device [%s] not exist!\n",devid);
	return -1;
    }
    pattr = cache_get_addr(pattr,cache_flag);
    if(NULL==pattr)
	return -1;

    int ret = -1;
    pthread_rwlock_rdlock(&pattr->rwlock);
    if(pattr->reader_cnt<MAX_READER_CNT)
    {
	int i = 0;
	pattr->reader_cnt += 1;
	if(pattr->reader_cnt<=0) 
	    pattr->reader_cnt = 1;
	for(i=0;i<MAX_READER_CNT;i++)
	{
	    if(pattr->reader_funit_pos[i]<0)
	    {
		pattr->reader_funit_pos[i] = pattr->last_iframe_pos;
		ret = i; // cache reader ID
		break;
	    }
	}
    }
    else
    {
	fprintf(stderr,"one device has too many readers[%d]\n",MAX_READER_CNT);
	ret = -1;
    }
    pthread_rwlock_unlock(&pattr->rwlock);
    return ret;
}

int cache_add_reader(const char *devid)
{
    int stream_rid = -1,data_rid = -1;

    stream_rid = cache_add_reader2(devid,CACHE_FLAG_STREAM);
    data_rid = cache_add_reader2(devid,CACHE_FLAG_TOAPPD);
    if(stream_rid<0 || data_rid<0)
	return -1;
    else
	return (data_rid<<8)|stream_rid;
}

int cache_del_reader2(const char *devid,int cache_flag,int rid)
{
    assert(g_pcache_index);
    assert(devid);

    st_cache_attr *pattr = cache_index_find(devid);
    if(NULL == pattr)
    {
	fprintf(stderr,"device [%s] not exist!\n",devid);
	return -1;
    }
    pattr = cache_get_addr(pattr,cache_flag);
    if(NULL==pattr)
	return -1;

    int ret = 0;
    pthread_rwlock_rdlock(&pattr->rwlock);
    pattr->reader_funit_pos[rid] = -1;
    pattr->reader_cnt -= 1;
    pthread_rwlock_unlock(&pattr->rwlock);
    return ret;
}

int cache_del_reader(const char *devid,int rid)
{
    int ret = 0;
    assert(devid);
    ret = cache_del_reader2(devid,CACHE_FLAG_STREAM,rid&0xff);
    ret = cache_del_reader2(devid,CACHE_FLAG_TOAPPD,(rid>>8)&0xff);
    return ret;
}

